package cn.tedu;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*需求：设计多线程编程模型，4个窗口共售票100张
 * 本类通过实现Runnable接口的方式实现多线程售票案例**/
public class TestRunnable {
    public static void main(String[] args) {
        //6.创建目标业务
        TicketRunnble target = new TicketRunnble();

//        //7.使用Thread类中的含参构造，将目标对象与线程对象做绑定
//        Thread t1 = new Thread(target);
//        Thread t2 = new Thread(target);
//        Thread t3 = new Thread(target);
//        Thread t4 = new Thread(target);
//        //8.以多线程的方式启动线程
//        t1.start();
//        t2.start();
//        t3.start();
//        t4.start();
        /*ExecutorService:用来储存线程的池子，把新建线程/启动线程/关闭线程的任务都交给池来管理*/
        /*Executors:用来创建线程池对象的工具
        * newFixedThreadPool(5)：表示创建含有5个线程数的池子*/
        //创建线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(5);
        //通过循环，让线程池中的线程开始执行任务
        for(int i = 0;i<5;i++){
            /*execute(target);让线程池中的线程来执行任务，每次调用都会启动一个线程*/
            pool.execute(target);/*本方法的参数就是要执行的目标业务target */
        }

    }

}
/*多线程中出现数据安全隐患的原因：
* 多线程程序 + 有共享数据（成员变量） + 多条语句操作共享数据**/
/* 同步锁：相当于给容易出现问题的代码加了一把锁，包裹所有可能会出现安全隐患的代码
* 加锁之后，就有同步（排队）的效果，但是加锁的范围需要考虑：
* 范围不能太大，干啥都得排队，效率低，范围太小，锁不住，还是会有安全隐患**/
//1.创建自定义多线程类
class TicketRunnble implements Runnable{
    //3.创建成员变量用来保存票数，注意必须是静态的
    static int tickets = 100;
    //9.2创建一个唯一的锁对象,
    Object o = new Object();
    //2.添加接口中未实现的方法，把业务放在run()里
    /*如果一个方法中的所有代码都需要同步，那这个方法可以设置成同步方法*/
    //synchronized public void run(){/*被synchronized关键字修饰的方法是同步方法*/
    @Override
    public void run() {
        while (true){
            /*同步代码块：sychronized（锁对象）{会出现安全问题的代码}
            * 同步代码块中，同一时刻，同一资源，只能被一个线程独享*/


            //9.1使用同步代码块包裹可能会出现安全隐患的代码，锁对象类型任意
            /*这种写法不对，相当于每一个线程进来都会new一个锁对象，线程间并不是使用同一把锁*/
            // synchronized (new Object()){
            synchronized (o){
            //9.3修改同步代码块的锁对象为成员变量o，注意“唯一”很重要
                //获取当前正在卖票的线程名称以及卖票
                if(tickets>0){
                    try {
                        //让程序休眠后的两个问题
                        //问题1：重卖，同一张票买给了多个人
                        //问题2：超卖：超出了规定票数，甚至卖出了0和-1这样的票数
                        Thread.sleep(10);//让程序休眠10毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"="+tickets--);
                }
            //4.获取当前正在卖票的线程名称，以及卖票
            if(tickets<=0)break;
            }
        }

    }
}
